おきらくPerlプログラミング入門
                      〜〜めざせ Perl マスター〜〜

                                広井  誠

                                 最終回




○タイ(tie)

  この講座も最終回となりましたが、最後は変数とクラスを結び付ける働きをす

るタイ(tie) について説明します。タイは、リファレンスやオブジェクト指向と

ともに、Perl 5 から追加された機能です。タイは、変数とユーザー定義のクラ

スを結び付ける働きをし、変数の読み出しや書き込みのタイミングで、特定のメ

ソッドを呼び出すことができます。変数には、スカラー、配列、ハッシュ、ファ

イルハンドルを指定することができます。タイを使うことで、特定の変数を監視

するといったデバッグツールの作成や、データベースとハッシュを結び付けるこ

とで、データベースへのアクセスを簡単に行うことができます。


○スカラーとタイ

  まずは、スカラーから説明しましょう。変数とクラスを結び付けるには、関数 

tie を使います。


    tie variable, classname, list;  


関数 tie は変数 variable をクラス classname に結び付けます。このように結

び付けられた変数をタイ変数といいます。tie は、クラス classname に定義さ

れている特別なメソッドを呼び出します。これは変数の種類によって異なります。


        変数の種類       |呼び出されるメソッド
        -----------------+--------------------
        スカラー         |  TIESCALAR
        配列             |  TIEARRAY
        ハッシュ         |  TIEHASH
        ファイルハンドル |  TIEHANDLE  


スカラーの場合は、TIESCALAR が呼び出されます。この時、tie に与えられた

list が引数として渡されます。つまり、TIESCALAR は次のように呼び出されま

す。


        classname->TIESCALAR( list );


tie から呼び出されるこれらのメソッドは、オブジェクトを返さなければいけま

せん。このオブジェクトと変数 [*1] が結び付けられます。

  スカラーがタイ変数になると、変数にアクセスするたびに、次のメソッドが呼

び出されます。


       アクション   | 呼び出されるメソッド
       -------------+--------------------------
       読み出し     | $obj->FETCH();
       書き込み     | $obj->STORE( $new_value );
       廃棄         | $obj->DESTROY();


廃棄には、関数 untie によってタイを解除する、関数 undef によって変数を未

定義にする、局所変数が有効範囲から出る(スコープの終了)ことの3通りがあ

ります。変数がタイ変数となると、変数としての機能が失われる、つまり、値の

読み書きができなくなることに注意してください。値を保存したい場合は、オブ

ジェクトにインスタンス変数を用意して、メソッド FETCH で変数の値を読み出

す、STORE で変数へ値を書き込む処理が必要になります。


  それでは簡単な実行例として、スカラー変数のアクセスを監視するプログラム

を作ってみましょう。最初にクラス MonScalar を定義します。


  package MonScalar;

  sub TIESCALAR {
    my ($pkg, $name, $value) = @_;
    my $obj = { name => $name, value => $value };
    bless $obj, $pkg;
    $obj;
  }

  sub FETCH {
    my $obj = shift;
    print 'Read : $', $obj->{'name'}, " -> $obj->{'value'}¥n";
    $obj->{'value'};
  }

  sub STORE {
    my ($obj, $new_value) = @_;
    print 'Write : $', $obj->{'name'}," <- $new_value¥n";
    $obj->{'value'} = $new_value;
  }

  sub DESTROY {
    print "DESTORY¥n";
  }


  TIESCALAR には引数として、変数名とその値を渡します。オブジェクトとして

無名のハッシュを生成して、変数名は name に、値は value にセットします。

後はオブジェクトをブレスして返すだけです。

  FETCH は簡単です。オブジェクトから名前と値を取り出して print で出力し

ます。FETCH の返り値がその変数の値として扱われるので、値をそのまま返しま

す。STORE も簡単ですね。書き込まれる値が引数として渡されるので、それをオ

ブジェクトの value に保存するだけです。

  それでは実行してみましょう。


  package main;

  $x = 10;
  tie $x, 'MonScalar', 'x', $x;
  $y = $x;
  print "y = $y¥n";
  $x = 100;
  $z = $x;
  print "z = $z¥n";
  untie $x;
  print "x = $x¥n";


  実行結果

  Read : $x -> 10
  y = 10
  Write : $x <- 100
  Read : $x -> 100
  z = 100
  DESTORY
  x = 100


  最初に tie を使って変数 $x とクラス MonScalar を結び付けます。tie の返

り値は、TIESCALAR で返すオブジェクトです。このオブジェクトを使って特別な

メソッド FETCH や STORE を呼び出すことができます。また、tied 関数を使っ

て、変数に結び付けられたオブジェクトを取り出すこともできます。たとえば、

$x = 100; は (tied $x)->STORE( 100 ); と同じ動作になります。

  この後 $x にアクセスするたびに、MonScalar のメソッド FETCH と STORE が

呼び出されます。untie を実行すると、メソッド DESTORY が呼び出されます。

ここで変数 $x の値に注意してください。参考文献によると、「タイされた変数

について untie 関数を呼び出すと、変数の値は元に復元される」とのことです。

ところが、$x は書き換えた値 100 になっています。どうやらスカラーの場合は、

元の値には復元されないようです。配列やハッシュでは、元の値に復元されます。

Windows で動作する ActivePerl 5.005 でも、スカラーの値は復元されませんが、

配列とハッシュの値は元に戻ります。スカラーでも元の値に復元される処理系が

あるかもしれません。そこで、untie されると変数の値は復元されることを前提

に話を進めます。


  [*1]  正確に説明すると、変数に格納されているデータ(スカラー、配列、ハッ
        シュやファイルハンドル)とオブジェクトが結び付けられます。変数自
        身と結びつくわけではないことに注意してください。


○変数の値を更新する

  監視を止めると変数の値が元に戻るようでは、監視の役目を果たしているとは

いえません。かえって、ユーザーを混乱させるだけです。変数の値は、オブジェ

クトに格納されているので、監視を止める時に値を更新することにしましょう。

この処理をメソッド unmonitor で行うことにします。

  [注意]  X68k 版 Perl 5 では関数 tied を呼び出すことができないので(未
          実装?)、これ以降のプログラムは ActivePerl 5.005 で動作チェッ
          クを行いました。


  # 監視を止める
  sub unmonitor {
    my ($pkg, $rvar) = @_;
    my $obj = tied $$rvar;
    my $last_value = $obj->{'value'};
    untie $$rvar;
    $$rvar = $last_value;
  }


このメソッドは MonScalar->unmonitor( ¥$x ); と呼び出します。リファレンス

を使って変数を渡すことに注意してください。関数 tied でオブジェクトを取り

出し、そこに格納されている値を $last_value にセットします。その後、untie

でタイを解除してから、変数の値を $last_value に更新します。


  これで正常に動作するように思いますが、実は不具合があるのです。-w オプ

ションを付けて perl を実行すると、次のメッセージが表示されます。


  untie attempted while 1 inner references still exist ...

    
これは局所変数 $obj がタイ変数のオブジェクトを参照しているため、オブジェ

クトのリファレンスカウントが 0 にならず、untie がオブジェクトを廃棄でき

ないことを表しています。この問題は、局所変数 $obj の有効範囲を限定するこ

とで解決することができます。


  # 監視を止める(修正版)
  sub unmonitor {
    my ($pkg, $rvar) = @_;
    my $last_value;
    {
      my $obj = tied $$rvar;
      $last_value = $obj->{'value'};
      $obj->{'name'} = '__UNMONITOR__'
    }
    untie $$rvar;
    $$rvar = $last_value;
  }


局所変数 $obj をブロック {} 内で定義します。こうすることで $obj の有効範

囲をブロック内に限定することができます。ブロックから抜けた時点で $obj は

無効となり、オブジェクトのリファレンスカウントが -1 されるため、untie で

オブジェクトを廃棄することができます。

  また、untie される時にメソッド DESTORY が呼び出されますが、変数の監視

を止めたわけですから、DESTROY のメッセージを出力しない方が良いでしょう。

そこで、オブジェクトの name に __UNMONITOR__ をセットし、DESTROY でチェッ

クすることにします。


  # 修正版  
  sub DESTROY {
    my $obj = shift;
    if( $obj->{'name'} ne '__UNMONITOR__' ){
      print 'DESTORY $', "$obj->{'name'}¥n";
    }
  }


プログラムの修正は簡単ですね。オブジェクトから name の値を取り出し、それ

を __UNMONITOR__ と比較するだけです。違っていれば、メッセージを出力しま

す。

  それでは、untie を unmonitor に変更して、実行してみましょう。


  $x = 10;
  tie $x, 'MonScalar', 'x', $x;
  $y = $x;
  print "y = $y¥n";
  $x = 100;
  $z = $x;
  print "z = $z¥n";
  MonScalar->unmonitor( ¥$x );
  print "x = $x¥n";


  実行結果

  Read : $x -> 10
  y = 10
  Write : $x <- 100
  Read : $x -> 100
  z = 100
  x = 100


監視を止めた後でも $x の値が 100 となります。正常に動作していますね。


○配列とタイ

  次は配列です。配列にタイを適用する場合、個々の要素へのアクセスの他にも、

push, pop など関数を使ったアクセスがあります。ところが、現在のところ配列

のタイでサポートされている操作は、要素の読み書きのみです。まあ、近い将来

に改善されると思います。
  
  配列の場合、tie が実行されると TIEARRAY が呼び出されます。そして、各要

素にアクセスするたびに、次のメソッドが呼び出されます。


       アクション  | 呼び出されるメソッド
       ------------+------------------------------------
       読み出し    | $obj->FETCH( $index );
       書き込み    | $obj->STORE( $index, $new_value );
       廃棄        | $obj->DESTROY();


メソッドの名前はスカラーと同じですが、アクセスした要素の添字が渡されるこ

とに注意してください。

  それでは簡単な実行例として、配列のアクセスを監視するプログラムを作って

みましょう。最初にパッケージ MonArray を定義します。


  package MonArray;

  sub TIEARRAY {
    my ($pkg, $name, $ra) = @_;
    my $obj = {
      name => $name, array => [@$ra],
    };
    bless $obj, $pkg;
    $obj;
  }

  sub FETCH {
    my ($obj, $index) = @_;
    my $value = $obj->{'array'}->[$index];
    print 'Read : $',$obj->{'name'}, "[$index] -> $value¥n";
    $value;
  }

  sub STORE {
    my ($obj, $index, $new_value) = @_;
    print 'Write : $',$obj->{'name'}, "[$index] <- $new_value¥n";
    $obj->{'array'}->[$index] = $new_value;
  }

  sub DESTROY {
    my $obj = shift;
    if( $obj->{'name'} ne '__UNMONITOR__' ){
      print 'DESTORY @', "$obj->{'name'}¥n";
    }
  }

  sub unmonitor {
    my ($pkg, $rvar) = @_;
    my $last_array;
    {
      my $obj = tied @$rvar;
      $last_array = $obj->{'array'};
      $obj->{'name'} = '__UNMONITOR__'
    }
    untie @$rvar;
    @$rvar = @$last_array;
  }


  スカラーと同様に、TIEARRAY には配列名と配列そのものを渡します。配列を

渡す時はリファレンスを使った方が簡単です。次にオブジェクトを生成し、名前

を name に、配列は無名の配列にコピーして array にセットします。後はオブ

ジェクトをブレスして返すだけです。FETCH と STORE は簡単です。引数として

渡された添字で、オブジェクトに格納された無名の配列にアクセスすればいいわ

けです。監視を止める場合は、スカラーと同様に unmonitor を使います。オブ

ジェクトに格納されている配列を、監視していた配列に代入するだけです。

  それでは実行してみましょう。

  package main;

  @a = (10, 20, 30);
  tie @a, 'MonArray', 'a', ¥@a;
  $x = $a[1];
  print "x = $x¥n";
  $a[1] = 200;
  $y = $a[1];
  print "y = $y¥n";
  MonArray->unmonitor( ¥@a );
  print "@a¥n";


  実行結果

  Read : $a[1] -> 20
  x = 20
  Write : $a[1] <- 200
  Read : $a[1] -> 200
  y = 200
  10 200 30


  最初に tie を使って配列 @a とクラス MonArray を結び付けます。この後 @a 

の要素にアクセスすると、MonArray のメソッド FETCH と STORE が呼び出され

ます。unmonitor で監視を止めた後でも、配列の値はきちんと更新されています

ね。


○ハッシュとタイ

  配列の場合と異なり、タイによるハッシュへのアクセスは、個々の要素へのア

クセス、ハッシュ全体の操作、関数による操作の全てを完全にサポートしていま

す。

  ハッシュの場合、tie が実行されると TIEHASH が呼び出されます。そして、

ハッシュにアクセスするたびに、次のメソッドが呼び出されます。


        操作例             | 呼び出されるメソッド
        -------------------+----------------------
        $h{a};             | $obj->FETCH('a');
        $h{a} = 1;         | $obj->STORE('a', 1);
        delete $h{a};      | $obj->DELETE('a');
        exists $h{a};      | $obj->EXISTS('a');
        %h = ();           | $obj->CLEAR();
                           |
        %h = ( a => 1 );   | $obj->CLEAR();
                           | $obj->STORE('a');
                           |
        keys, values, each | $lk = $obj->FIRSTKEY();
                           | do {
                           |   $val = $obj->FETCH($lk);
                           | } while ( $lk = $obj->NEXTKEY($lk) );


  FIRSTKEY は最初のキーを、NEXTKEY は次のキーを返すように作る必要があり

ます。keys ではこの2つのメソッドが呼び出され、values や each では、各々

のキーについて FETCH が呼び出されます。

  定義するメソッドが多くで面倒だと感じる方は、Tie::Hash, Tie::StdHash と

いう、タイハッシュに対する基本クラスを定義したモジュールが標準ライブラリ

に用意されているので、これを継承するといいでしょう。


  それでは簡単な実行例として、ハッシュのアクセスを監視するプログラムを作っ

てみましょう。簡単な例題ということで、要素へのアクセスとハッシュ全体をク

リアする操作だけを監視することにします。最初にパッケージ MonHash を定義

します。


  package MonHash;

  sub TIEHASH {
    my ($pkg, $name, $rh) = @_;
    my $obj = {
      name => $name, hash => {%$rh},
    };
    bless $obj, $pkg;
    $obj;
  }

  sub FETCH {
    my ($obj, $index) = @_;
    my $value = $obj->{'hash'}->{$index};
    print 'Read : $', $obj->{'name'}, "{$index} -> $value¥n";
    $value;
  }

  sub STORE {
    my ($obj, $index, $new_value) = @_;
    print 'Write : $', $obj->{'name'}, "{$index} <- $new_value¥n";
    $obj->{'hash'}->{$index} = $new_value;
  }

  sub CLEAR {
    my $obj = shift;
    print 'Clear : %', "$obj->{'name'}¥n";
    $obj->{'hash'} = {};
  }

  sub DESTROY {
    my $obj = shift;
    if( $obj->{'name'} ne '__UNMONITOR__' ){
      print 'DESTORY %', "$obj->{'name'}¥n";
    }
  }

  sub unmonitor {
    my ($pkg, $rvar) = @_;
    my $last_hash;
    {
      my $obj = tied %$rvar;
      $last_hash = $obj->{'hash'};
      $obj->{'name'} = '__UNMONITOR__'
    }
    untie %$rvar;
    %$rvar = %$last_hash;
  }


  スカラーや配列と同様に、TIEHASH にはハッシュ名とハッシュそのものを渡し

ます。ハッシュを渡す時はリファレンスを使います。次にオブジェクトを生成し、

名前を name に、ハッシュの値は無名のハッシュにコピーして hash にセットし

ます。後はオブジェクトをブレスして返すだけです。FETCH と STORE は簡単で

す。引数として渡された添字で、オブジェクトに格納された無名のハッシュにア

クセスすればいいわけです。CLEAR はもっと簡単ですね。メッセージを出力した

ら、ハッシュを空にするだけです。unmonitor も配列をハッシュに変えただけで

す。

  それでは実行してみましょう。

  package main;

  %h = (a => 10, b => 20, c => 30);
  print %h, "¥n";
  tie %h, 'MonHash', 'h', ¥%h;
  $x = $h{'b'};
  print "x = $x¥n";
  $h{'b'} = 200;
  $y = $h{'b'};
  print "y = $y¥n";
  %h = ( a => 100, b => 200, c => 300 );
  MonHash->unmonitor( ¥%h );
  print %h;


  実行結果

  a10b20c30
  Read : $h{b} -> 20
  x = 20
  Write : $h{b} <- 200
  Read : $h{b} -> 200
  y = 200
  Clear : %h
  Write : $h{a} <- 100
  Write : $h{b} <- 200
  Write : $h{c} <- 300
  a100b200c300


  もう説明しなくてもいいですね。正常に動作しています。


○パッケージ Monitor の作成 

  ここまで、スカラー、配列、ハッシュの監視プログラムを作りましたが、いち

いちクラスを指定して tie 関数を呼び出すのは面倒ですね。そこで、与えられ

た変数の種類を調べて、適切な tie 関数を呼び出すプログラムを作りましょう。

作成するプログラムは、変数を監視する monitor と監視を止める unmonitor の

2つです。monitor は変数のリファレンスと名前を、unmonitor は変数のリファ

レンスを引数として受け取ります。


  # 使用例
  $x = 100;
  monitor( ¥$x, 'x' );
  unmonitor( ¥$x );
  

monitor と unmonitor はパッケージ Monitor に定義します。プログラムは次

のようになります。


  # パッケージの定義(Monitor.pm)
  package Monitor;
  use Exporter;
  @ISA = (Exporter);
  @EXPORT_OK = ('monitor', 'unmonitor');

  # 変数を監視する
  sub monitor {
    my ($rvar, $name) = @_;
    my $type = ref( $rvar );     # 型のチェック
    if( $type eq 'SCALAR' ){
      tie $$rvar, 'MonScalar', $name, $$rvar;
    } elsif( $type eq 'ARRAY' ){
      tie @$rvar, 'MonArray', $name, $rvar;
    } elsif( $type eq 'HASH' ){
      tie %$rvar = 'MonHash', $name, $rvar;
    } else {
      print STDERR "リファレンスが必要です¥n";
    }
  }

  # 変数の監視を止める
  sub unmonitor {
    my $rvar = shift;
    my $type = ref( $rvar );     # 型のチェック
    if( $type eq 'SCALAR' ){
      MonScalar->unmonitor( $rvar );
    } elsif( $type eq 'ARRAY' ){
      MonArray->unmonitor( $rvar );
    } elsif( $type eq 'HASH' ){
      MonHash->unmonitor( $rvar );
    } else {
      print STDERR "リファレンスが必要です¥n";
    }
  }


2つのプログラムともに、関数 ref を呼び出してリファレンス先のデータの種

類を調べ、適切な関数を呼び出すだけです。MonScalar, MonArray, MonHash は

ファイル Monitor.pm にまとめて定義しておきます。


  それでは実行例を示します。


  # テストプログラム(montest.pl)
  use Monitor ('monitor', 'unmonitor');

  $x = 10;
  @a = (100, 200, 300);
  %h = (a => 1, b => 2, c => 3 );

  monitor( ¥$x, 'x' );
  monitor( ¥@a, 'a' );
  monitor( ¥%h, 'h' );

  $x = 20;
  $y1 = $x;
  print "y1 = $y1¥n";

  $a[2] = 3000;
  $y2 = $a[2];
  print "y2 = $y2¥n";

  $h{'c'} = 30;
  $y3 = $h{'c'};
  print "y3 = $y3¥n";

  unmonitor( ¥$x );
  unmonitor( ¥@a );
  unmonitor( ¥%h );

  print "x = $x¥n";
  print "a = @a¥n";
  print "h = ", %h, "¥n";


  実行結果

  Write : $x <- 20
  Read : $x -> 20
  y1 = 20
  Write : $a[2] <- 3000
  Read : $a[2] -> 3000
  y2 = 3000
  Write : $h{c} <- 30
  Read : $h{c} -> 30
  y3 = 30
  x = 20
  a = 100 200 3000
  h = a1b2c30


  正常に動作していますね。


  タイの最も有効な利用方法は、ハッシュとデータベースを結び付けることです

が、本講座の範囲を超えるので説明を割愛いたします。また、ファイルハンドル

とタイの説明も割愛させていただきます。興味のある方は参考文献を読んでくだ

さい。


○おわりに

  最近、インタプリタ形式のプログラミング言語(スクリプト言語ともいう)が

注目を集めています。その中で、Perl は CGI スクリプトを書くためのプログラ

ミング言語として、その普及度は目を見張るものがあります。この講座では、筆

者のスキル不足のため、CGI プログラミングを取り上げることはできませんでし

た。しかしながら、Perl の基本からオブジェクト指向まで、プログラミング言

語としての機能は一通り説明できたと思っています。

  Perl は応用範囲の広いプログラミング言語です。CGI やネットワークの他に

も、GUI ツールキット Tk を利用するためのモジュール Tk.pm [*2] をロードす

ることで、Perl でも GUI アプリケーションを作成することができます。これか

らも、いろいろな分野で Perl が利用されることでしょう。皆さんも Perl を使っ

てプログラミングを楽しんでください。

  最後になりましたが、この講座が少しでも皆様のお役に立てれば、筆者として

はこれほどの幸せはありません。長い間お付き合いいただいた読者の皆様、なら

びに編集スタッフの方々に感謝の意を表します。


    [*2]    Perl と Tk.pm の組み合わせを「Perl/Tk」といいます。なお、
          Perl/Tk を実行するために Tcl/Tk は必要ありません。use Tk; で 
          Tk の利用が可能になります。
            Perl のモジュールは総合 Perl アーカイブネットワーク (CPAN) 
          からダウンロードすることができます。ActivePerl 用 Tk.pm の入手
          先は、須栗歩人氏のホームページ 

                  http://members.xoom.com/tcltk/index.html 

          を参照してください。Tcl/Tk や Perl/Tk の情報がとても参考になり
          ます。


                              ―参考文献―

 [1] Larry Wall, Tom Christiansen, Randal L. Schwartz 共著「プログラミン
     グPerl」改訂版 オライリー・ジャパン 1997

 [2] Sriram Srinivasan  著「実用Perlプログラミング」オライリー・ジャ
     パン 1998

(EOF)